<?php
	require('classes/DatabaseClass.php');
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * JSON_RPC_Server
 *
 * @package JSON_RPC
 * @author Matt
 * @version SVN: $Id$
 */


class JSON_RPC_Server
{
	// JSON-RPC version (see : http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html)
	const JSON_RPC_VERSION = '1.1';
	// Service description version (see : http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html#ServiceDescription)
	const SERVICE_DESCRIPTION_VERSION = '1.0';   
	
	//const JSON_RPC_CONTENT_TYPE = 'application/json';
	const JSON_RPC_CONTENT_TYPE = 'text/plain';
   
    public $methods = array();

    protected $service 	= null;
    protected $obj 		= null;
    protected $req_id	= null;
    protected $reserved_methods 	= array('system.describe' => 'describeService');
    protected $_service_properties 	= array('name' 	=> 'REQUIRED', 
    										'id'	=> 'REQUIRED', 
    										'version'=>'OPTIONAL',
    										'summary'=>'OPTIONAL',
    										'help'	=> 'OPTIONAL',
    										'address'=>'OPTIONAL',
    										'procs'	=>'OPTIONAL'
    										);
	protected $socket_mode = false;    									    
	protected $socket_addr = '';    									    
    
	/**
	 * Constructor
	 *
	 * @param object $obj Class instance that we want to make available through JSON-RPC
	 * @param array $renamed_methods Map of methods we want to rename
	 * @access public
	 */
	public function __construct( $obj = null, $renamed_methods = array(), $socket_mode = false, $socket_addr = '' ) {
		$this->socket_mode = $socket_mode;
		$this->socket_addr = $socket_addr;
		if(!is_null($obj)) {
			$this->obj = $obj;
			foreach($renamed_methods as $internal_name => $external_name) {
				$this->methods[$external_name] = $internal_name; 	
			}
		}
    }
    
    /**
     * Declare Service
     *
     * @param array $serv_props
     * @access public
     */
    public function declareService( $serv_props = array() ) {
		// init $this->service to a simple stdClass
		$this->service = new stdClass();
    	foreach ($this->_service_properties as $key => $val) {
    		if( $val == 'REQUIRED' && ( !array_key_exists($key, $serv_props) || empty($serv_props[$key]) ) ) {
    			$this->error('InvalidServiceDescription', 503, 'Invalid service description');
    		}
    		if(array_key_exists($key, $serv_props)) {
    			$this->service->$key = $serv_props[$key];
    		}
    	}    	
    }
    
    /**
     * Register a simple function
     *
     * @param string $internal_name internal function name
     * @param string $external_name external function name, if different
     * @access public
     */
    public function registerFunction($internal_name, $external_name = '' ) {
    	$external_name = empty($external_name) ? $internal_name : $external_name;
		$this->methods[$external_name] = $internal_name;    	
    }
    
    /**
     * Start listening
     *
     * @access public
     * @todo Implementation of socket transport handling
     */
    public function run() {
    	// the server run in socket mode
    	echo "hi";
    	if($this->socket_mode == true) {
    		// the server run in HTTP mode
    	} else {
	    	// the server handles a HTTP POST request
	    	if($_SERVER['REQUEST_METHOD'] == 'POST') 
	    	{
	    		$json_data = file_get_contents('php://input');
				$data = json_decode($json_data, true);
				if(!$data) {
					$this->error("InvalidJSONRPCRequest", 501, "Invalid JSON request. JSON cannot be parsed. JSON was : ".$json_data);
				}    		
	    	} 
	    	// the server handles a HTTP GET request
	    	elseif ($_SERVER['REQUEST_METHOD'] == 'GET') 
	    	{
		    	// if magic quotes active, strip antislashes from data
		    	if(get_magic_quotes_gpc() == 1) {
					$_GET = array_map('stripslashes', $_GET);    		
		    	}
		    	// handle null id
		    	if($_GET['id'] == 'null' || !array_key_exists('id', $_GET)) {
		    		$_GET['id'] = null;
		    	}
		    	
		    	// we use Positional Parameters
		    	if(!isset($_GET['kwparams'])) {
			    	$data = array('method' => @$_GET['method'], 'id' => @$_GET['id'] );
			    	unset($_GET['method']);
			    	unset($_GET['id']);
			    	$data['params'] = $_GET;
			    
			    // we use named parameters
		    	}else{
		    		
		    		
		    	}
	    	}
	    	
	    	$this->req_id = $data['id'];
			define('JSONRPC_REQUEST_ID', $this->req_id);
			$this->callMethod($data['method'], $data['params']);
    	}
    } 
    
    /**
     * Return Service Description
     *
     * @access protected
     * @return json object
     */
    protected function getServiceDescription() {
    	return json_encode($this->service);
    }
    
    
    /**
     * Call method / function
     *
     * @param string $method
     * @param array $args
     * @access protected 
     */
    protected function callMethod($method, $args) {
    	if(array_key_exists($method, $this->methods)) {
    		$method = $this->methods[$method];
    	}
    	
    	if( !is_null($this->obj) && method_exists($this->obj, $method)) {
    		$call = @call_user_func_array(array($this->obj, $method), $args);
    	} elseif (function_exists($method)) {
    		$call = @call_user_func_array($method, $args);
    	} else{
    		return $this->error("UndefinedMethod", 502, "Method/function '$method' does not exist.");
    	}
    	
    	// function/method call worked, output result
    	$this->output($call);
    }
    
    /**
     * Output JSON-RPC response
     *
     * @param object $data result data
     * @param object $error error data
     * @access protected 
     */
    protected function output($data = null, $error = null) {
    	$response = new stdClass();
    	$response->version = JSON_RPC_Server::JSON_RPC_VERSION;
    	// there is some data to outupt
    	if(!is_null($data)) {
    		$response->result = $data;
    	}
    	// there was an error
    	if(!is_null($error)) {
    		$response->error = $error;
    	}else{
    		$response->error = null;
    	}
    	header('Content-Type: '.JSON_RPC_Server::JSON_RPC_CONTENT_TYPE);
    	
    	// final output
    	if(defined('JSONRPC_REQUEST_ID')) {
    		$response->id = JSONRPC_REQUEST_ID;
    	} else {
    		$response->id = $response->req_id;
    	}
   		echo json_encode($response);
    }

    /**
     * Throw an error
     *
     * @param string $err_name Error name
     * @param integer $err_code Error code
     * @param string $err_msg Error message
     * @access protected 
     */
    public function error($err_name, $err_code, $err_msg) {
    	$error 			= new stdClass();
    	$error->name 	= $err_name;
    	$error->code 	= $err_code;
    	$error->message = $err_msg;
    	$this->output(null, $error);
    }
}

// global error handling
function JSONRPC_ErrorHandler($errno, $errstr, $errfile, $errline)
{
	
	$json_server = new JSON_RPC_Server();
	
    switch ($errno) {
	    case E_USER_ERROR:
	    	$json_server->error('JSONRPC_PHPError', 504, "$errstr");
	        exit(1);
	        break;
	    case E_USER_WARNING:
	    case E_USER_NOTICE:
	    case E_NOTICE:
	    case E_USER_NOTICE:
	    case E_STRICT:
	        break;
	    default:
	        $json_server->error('JSONRPC_PHPError', 505, "$errstr");
	        exit(1);
	        break;
    }

    /* Ne pas exécuter le gestionnaire interne de PHP */
    return true;
}
set_error_handler('JSONRPC_ErrorHandler');
/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * c-hanging-comment-ender-p: nil
 * End:
 */
?>